/***************************************************************************
 *   Copyright (C) 2010 by Peter Hatina                                    *
 *   email: phatina (at) gmail.com                                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License           *
 *   version 2.1 as published by the Free Software Foundation              *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Lesser General Public License for more details.                   *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.               *
 ***************************************************************************/

#include "batterymetergui.h"
#include "settings.h"
#include "action.h"
#include "shutdown.h"
#include "common.h"

#ifdef Q_OS_WIN32
#  include "winregistry.h"
#  include "powerscheme.h"
#endif

#include <limits>
#include <QApplication>
#include <QProcess>
#include <QDateTime>
#include <QFile>

//log time (ms)
const int LOGGER_TIMEOUT = 60000;

BatteryMeterGui::BatteryMeterGui(QObject *parent):
    QTimer(parent),
    m_tray(new BatteryMeterTrayIcon),
    m_menu(new QMenu),
    m_act_quit(new QAction(QIcon(":/img/exit.png"), "Quit", m_menu.data())),
    m_act_settings(new QAction(QIcon(":/img/settings.png"), "Settings", m_menu.data())),
    m_act_info(new QAction(QIcon(":/img/info.png"), "Info", m_menu.data())),
    m_act_log(new QAction(QIcon(":/img/log.png"), "Log", m_menu.data())),
#if (defined (Q_OS_WIN32) && (WINVER >= 0x0600))
    m_power_menu(new QMenu()),
    m_actgrp_power(new QActionGroup(m_power_menu.data())),
    m_act_min_power(new QAction("Power saver", m_power_menu.data())),
    m_act_max_power(new QAction("Performance", m_power_menu.data())),
    m_act_balanced(new QAction("Balanced", m_power_menu.data())),
#endif
    m_dlg_info(new InfoDialog),
    m_dlg_settings(new SettingsDialog)
{
    connect(m_dlg_settings.data(), SIGNAL(newConfiguration()), this, SLOT(readConfiguration()));

    // fill tray  menu
    m_menu->addAction(m_act_info.data());
    m_menu->addAction(m_act_log.data());
    m_menu->addAction(m_act_settings.data());
#if (defined (Q_OS_WIN32) && (WINVER >= 0x0600))
    m_power_menu.reset(m_menu->addMenu(QIcon(":/img/scheme.png"), "Power scheme"));
    m_act_min_power->setCheckable(true);
    m_act_min_power->setChecked(m_power_scheme.isMinPower());
    m_act_max_power->setCheckable(true);
    m_act_max_power->setChecked(m_power_scheme.isMaxPower());
    m_act_balanced->setCheckable(true);
    m_act_balanced->setChecked(m_power_scheme.isBalanced());
    m_actgrp_power->addAction(m_act_min_power);
    m_actgrp_power->addAction(m_act_max_power);
    m_actgrp_power->addAction(m_act_balanced);
    m_actgrp_power->setExclusive(true);
    m_power_menu->addAction(m_act_min_power);
    m_power_menu->addAction(m_act_balanced);
    m_power_menu->addAction(m_act_max_power);
    connect(m_act_min_power.data(), SIGNAL(triggered()), this, SLOT(changePowerStatus()));
    connect(m_act_max_power.data(), SIGNAL(triggered()), this, SLOT(changePowerStatus()));
    connect(m_act_balanced.data(), SIGNAL(triggered()), this, SLOT(changePowerStatus()));
    connect(&m_message_listener, SIGNAL(powerSchemeChanged()), this, SLOT(powerStatusChanged()));
    if (!m_power_scheme.powrProfLoaded())
    {
        m_menu->removeAction(m_power_menu->menuAction());
        m_dlg_settings->setUseSchemes(false);
    }
#endif
    m_menu->addSeparator();
    m_menu->addAction(m_act_quit.data());
    m_tray->setContextMenu(m_menu.data());
    m_tray->setIcon(QIcon());

#ifdef USE_LOGGING
    // logger
    m_logger.setInterval(LOGGER_TIMEOUT);
    connect(&m_logger, SIGNAL(timeout()), this, SLOT(onLog()));
#endif

    // action connections
    connect(this, SIGNAL(timeout()), this, SLOT(update()));
    connect(m_act_info.data(), SIGNAL(triggered()), this, SLOT(infoDlgVisibility()));
    connect(m_act_settings.data(), SIGNAL(triggered()), m_dlg_settings.data(), SLOT(show()));
    connect(m_tray.data(), SIGNAL(clicked()), this, SLOT(infoDlgVisibility()));
    connect(m_tray.data(), SIGNAL(hint()), m_dlg_info.data(), SLOT(show()));
    connect(m_act_quit.data(), SIGNAL(triggered()), qApp, SLOT(quit()));
    connect(m_act_quit.data(), SIGNAL(triggered()), m_tray.data(), SLOT(hide()));

    // read config. file
    readConfiguration();
}

BatteryMeterGui::~BatteryMeterGui()
{
}

void BatteryMeterGui::update()
{
    m_battery_meter.update();
    if (!m_battery_meter.batteryPresent() && m_battery_meter.acOnline())
    {
        // no battery present, we run only on an AC adaptor
        m_act_info->setEnabled(false);
        m_tray->setPercentage(100);
        return;
    }

    m_act_info->setEnabled(true);

    // prepare data for infoDlg
    QString charging_state;
    switch (m_battery_meter.state())
    {
    case BatteryMeter::CHARGING:
        charging_state = "Charging";
        m_tray->setAnimate(true);
        break;

    case BatteryMeter::CHARGED:
        charging_state = "Charged";
        m_tray->setAnimate(false);
        m_tray->setPercentage(m_battery_meter.percentage());
        break;

    case BatteryMeter::DISCHARGING:
        charging_state = "Discharging";
        m_tray->setAnimate(false);
        m_tray->setPercentage(m_battery_meter.percentage());
        break;

    case BatteryMeter::UNKNOWN:
        charging_state = "Unknown";
        m_tray->setAnimate(false);
        m_tray->setPercentage(100);
        break;
    }

    // setup info dialog
    m_dlg_info->setPercentage(m_battery_meter.percentage());
    m_dlg_info->setCapacity(m_battery_meter.remainCapa(), m_battery_meter.designCapa());
    m_dlg_info->setRate(m_battery_meter.rate());
    m_dlg_info->setBatteryState(m_battery_meter.state());
    m_dlg_info->setAcState(m_battery_meter.acOnline());
    m_dlg_info->setTimeRemaining(m_battery_meter.remainTime());

    // charged status tooltip
    QString toolTip = QString("Battery state: %1 % (").arg(m_battery_meter.percentage());
    toolTip += charging_state + ")";
    unsigned long mins = m_battery_meter.remainTime();
    QTime t(mins / 60, mins % 60);
    if (t.minute() != 0 || t.hour() != 0)
    {
        toolTip += QString("\nRemaining time: ") + t.toString("hh:mm");
    }
    else
    {
        if (!m_battery_meter.acOnline())
        {
            singleShot(5000, this, SLOT(update()));
        }
    }
    m_tray->setToolTip(toolTip);

    if (!m_tray->isVisible())
        return;

    static unsigned long prev_perc = std::numeric_limits <unsigned long>::max();

    //bool discharge = prev_perc > m_battery_meter.percentage()
    //	|| prev_perc == std::numeric_limits <unsigned long>::max();
    bool discharge = prev_perc >= m_battery_meter.percentage() &&
        m_battery_meter.state() == BatteryMeter::DISCHARGING;
    prev_perc = m_battery_meter.percentage();

    // process actions
    if (discharge)
        dischargeActions();
    else
        chargeActions();
}

void BatteryMeterGui::show()
{
    m_tray->show();
}

void BatteryMeterGui::hide()
{
    m_tray->hide();
}

void BatteryMeterGui::start()
{
    if (this->isActive())
        return;

    // run timer in seconds interval
    // interal comes from config
    Settings *settings = Settings::instance();
    QTimer::start(settings->updateInterval() * 1000);

#ifdef Q_OS_WIN32
    m_message_listener.listen();
    connect(&m_message_listener, SIGNAL(powerStatusChanged()), this, SLOT(update()));
#endif
}

void BatteryMeterGui::start(int msec)
{
    if (this->isActive())
        return;

    // run in ms interval
    QTimer::start(msec);

#ifdef Q_OS_WIN32
    m_message_listener.listen();
    connect(&m_message_listener, SIGNAL(powerStatusChanged()), this, SLOT(update()));
#endif
}

void BatteryMeterGui::stop()
{
#ifdef Q_OS_WIN32
    disconnect(&m_message_listener, SIGNAL(powerStatusChanged()), this, SLOT(update()));
#endif
}

#ifdef USE_LOGGING
void BatteryMeterGui::onLog()
{
    if (m_log_file.isEmpty())
    {
        // TODO maybe change the behavior
        m_logger.stop();
        return;
    }

    QFile fout(m_log_file);
    if (!fout.open(QIODevice::Append))
        return;

    QString str_line = QDateTime::currentDateTime().toString("yyyy.MM.dd-hh:mm:ss");
    QString perc = QString("%1").arg(m_battery_meter.percentage());
    while (perc.length() < 3)
        perc.prepend("0");
    str_line += "," + perc;
    str_line += QString(",%1").arg(m_battery_meter.acOnline() ? "conn" : "disc");
    str_line += "\n";
    QByteArray line;
    line.append(str_line);
    fout.write(line);

    fout.close();
}
#endif

void BatteryMeterGui::dischargeActions()
{
    while (!m_unprocessed_actions.isEmpty() &&
        (static_cast <unsigned long> (m_unprocessed_actions.last().level())
        >= m_battery_meter.percentage()))
    {
        // move the action into other container
        Action action = m_unprocessed_actions.last();
        m_unprocessed_actions.pop_back();
        m_processed_actions.push_front(action);

        // process the action
        switch (action.type())
        {
        case Action::MESSAGE:
            m_tray->showMessage("Battery Meter", action.attr(), QSystemTrayIcon::Information);
            break;

        case Action::ACTION:
            QProcess::execute(action.attr());
            break;

        case Action::SUSPEND:
            Shutdown::suspend();
            break;

        case Action::HIBERNATE:
            Shutdown::hibernate();
            break;

        case Action::POWEROFF:
            Shutdown::shutdown();
            break;

        default:
            break;
        }
    }
}

void BatteryMeterGui::chargeActions()
{
    while (!m_processed_actions.isEmpty() &&
           static_cast <unsigned long> (m_processed_actions.first().level())
           < m_battery_meter.percentage())
    {
        Action action = m_processed_actions.first();
        m_processed_actions.pop_front();
        m_unprocessed_actions.push_back(action);
    }

    //printActions();
}

#if (defined (Q_OS_WIN32) && (WINVER >= 0x0600))
void BatteryMeterGui::changePowerStatus()
{
    if (m_act_min_power->isChecked())
    {
        m_power_scheme.setMinPower();
    }
    else if (m_act_max_power->isChecked())
    {
        m_power_scheme.setMaxPower();
    }
    else
    {
        m_power_scheme.setBalanced();
    }
}

void BatteryMeterGui::powerStatusChanged()
{
    m_act_min_power->setChecked(m_power_scheme.isMinPower());
    m_act_max_power->setChecked(m_power_scheme.isMaxPower());
    m_act_balanced->setChecked(m_power_scheme.isBalanced());

    m_dlg_settings->setMinPower(m_act_min_power->isChecked());
    m_dlg_settings->setMaxPower(m_act_max_power->isChecked());
    m_dlg_settings->setBalanced(m_act_balanced->isChecked());
}
#endif

void BatteryMeterGui::infoDlgVisibility()
{
    if (!m_battery_meter.batteryPresent())
        return;

    m_dlg_info->isVisible() ? m_dlg_info->hide() : m_dlg_info->show();
}

void BatteryMeterGui::readConfiguration()
{
    Settings *settings = Settings::instance();

    // load application settings
    m_unprocessed_actions = settings->actions();
    m_processed_actions.clear();
#ifdef USE_LOGGING
    m_logging = settings.logEnabled() && settings.log();
    m_log_file = settings.logFile();
#endif
    setInterval(settings->updateInterval() * 1000);
    // TODO some time, maybe it will be ready
    //m_act_log->setVisible(m_logging);
    m_act_log->setVisible(false);

    // move passed events into processed list
    while (!m_unprocessed_actions.isEmpty() &&
           (static_cast <unsigned long> (m_unprocessed_actions.last().level())
            >= m_battery_meter.percentage()))
    {
        Action action = m_unprocessed_actions.last();
        m_unprocessed_actions.pop_back();
        m_processed_actions.push_front(action);
    }
#ifdef USE_LOGGING
    if (m_logging)
        m_logger.start();
    else
        m_logger.stop();
#endif
}

//------------------------------------------------------------------------------

#ifdef Q_OS_WIN32
MessageListener::MessageListener(QWidget *parent):
    QWidget(parent),
    m_listen(false)
{
}

MessageListener::~MessageListener()
{
}

void MessageListener::listen(bool l)
{
    if (m_listen == l)
        return;

    m_listen = l;

#  if (WINVER > 0x0600)
    if (m_listen)
    {
        // register for the message
        m_power_source_notify = RegisterPowerSettingNotification(
                    reinterpret_cast <void *> (winId()),
                    &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);

        m_battery_perc_notify = RegisterPowerSettingNotification(
                    reinterpret_cast <void *> (winId()),
                    &GUID_BATTERY_PERCENTAGE_REMAINING, DEVICE_NOTIFY_WINDOW_HANDLE);

        m_power_scheme_notify = RegisterPowerSettingNotification(
                    reinterpret_cast <void *> (winId()),
                    &GUID_POWERSCHEME_PERSONALITY, DEVICE_NOTIFY_WINDOW_HANDLE);
    }
    else
    {
        UnregisterPowerSettingNotification(m_power_source_notify);
        UnregisterPowerSettingNotification(m_battery_perc_notify);
        UnregisterPowerSettingNotification(m_power_scheme_notify);
    }
#  endif
}

bool MessageListener::winEvent(MSG *msg, long *res)
{
    if (!m_listen)
        return true;

    if (msg->message == WM_POWERBROADCAST)
    {
#  if (WINVER >= 0x0600)
        //windows vista, 7
        if (msg->wParam == PBT_POWERSETTINGCHANGE)
        {
            POWERBROADCAST_SETTING *setting = reinterpret_cast
                    <POWERBROADCAST_SETTING *> (msg->lParam);

            if (sizeof(int) == setting->DataLength &&
                    (setting->PowerSetting == GUID_ACDC_POWER_SOURCE ||
                     setting->PowerSetting == GUID_BATTERY_PERCENTAGE_REMAINING))
            {
                // change from ac to battery or back
                // or battery cap. comes up and down
                emit powerStatusChanged();
            }
            else if (sizeof(GUID) == setting->DataLength &&
                     setting->PowerSetting == GUID_POWERSCHEME_PERSONALITY)
            {
                emit powerSchemeChanged();
            }
        }
#  else
        // windows 2000 pro - xp
        if (msg->wParam == PBT_APMPOWERSTATUSCHANGE)
        {
            // change from ac to batt or back
            emit powerStatusChanged();
        }
#  endif

        // do not process this message by Qt
        return true;
    }

    return false;
}
#endif
